package jmine.tec.security.josso.agent;

import java.security.Principal;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.security.auth.Subject;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.LogFactory;
import org.josso.agent.Lookup;
import org.josso.agent.SSOAgent;
import org.josso.gateway.Constants;
import org.josso.gateway.identity.SSORole;
import org.josso.gateway.session.SSOSession;
import org.josso.gateway.session.exceptions.NoSuchSessionException;
import org.josso.gateway.session.exceptions.SSOSessionException;

import bancosys.tec.security.SecurityException;
import bancosys.tec.security.authorization.Permission;
import bancosys.tec.security.impl.AbstractSecurityManager;
import bancosys.tec.security.impl.SecurityPrincipal;
import bancosys.tec.security.impl.domain.User;
import bancosys.tec.security.impl.web.WebSecurityContext;
import bancosys.tec.security.impl.web.WebSecurityManager;
import bancosys.tec.security.impl.web.manager.HttpApplicationSecurityManager;

/**
 * {@link WebSecurityManager} que usa o JOSSO para autenticar. Antes de ser usado, este securityManager deve ter a propriedade
 * {@link #requester} setada
 * 
 * @author takeshi
 */
public class JOSSOSecurityManager extends AbstractSecurityManager<WebSecurityContext> implements WebSecurityManager {

    private static final String JOSSO_ID_SESSION_KEY = "protected" + WebSecurityManager.class.getName() + ".JOSSOSessionID";

    private String requester;

    /**
     * {@inheritDoc}
     */
    public Subject loadSubject(WebSecurityContext context) throws SecurityException {
        if (context.getRequest().getSession() == null) {
            return null;
        }
        if (this.sessionHasValidJOSSOSessionID(context) && this.sessionHasStoredSubject(context)) {
            return this.loadSessionSubject(context);
        } else {
            context.getRequest().getSession().invalidate();
            Subject subject = this.loginAndStoreSubject(context);
            context.getRequest().getSession().setAttribute(JOSSO_ID_SESSION_KEY, getJossoSessionId(context));
            return subject;
        }
    }

    /**
     * Verifica se a session possui um JOSSO Session ID válido.
     * 
     * @param context context
     * @return boolean
     */
    private boolean sessionHasValidJOSSOSessionID(WebSecurityContext context) {
        final Object storedJossoSessionId = context.getRequest().getSession().getAttribute(JOSSO_ID_SESSION_KEY);

        boolean valid =
                storedJossoSessionId != null && !"-".equals(storedJossoSessionId)
                        && storedJossoSessionId.equals(this.getJossoSessionId(context));
        if (valid) {
            try {
                SSOAgent jossoAgent = Lookup.getInstance().lookupSSOAgent();
                return jossoAgent.getSSOSessionManager().getSession(this.requester, String.valueOf(storedJossoSessionId)).isValid();
            } catch (NoSuchSessionException e) {
                return false;
            } catch (SSOSessionException e) {
                return false;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return false;
    }

    /**
     * Verifica se a sessão possui um subject armazenado
     * 
     * @param context context
     * @return boolean
     */
    private boolean sessionHasStoredSubject(WebSecurityContext context) {
        return this.loadSessionSubject(context) != null;
    }

    /**
     * Carrega o subject armazenado na session
     * 
     * @param context context
     * @return subject
     */
    private Subject loadSessionSubject(WebSecurityContext context) {
        return HttpApplicationSecurityManager.loadSubjectFromWebSecurityContext(context);
    }

    /**
     * Efetua o login e armazena o subject na session
     * 
     * @param context context
     * @return Subject
     */
    private Subject loginAndStoreSubject(WebSecurityContext context) {
        Subject subject = this.login(null, null, context);
        this.storeSubject(context, subject);
        return subject;
    }

    /**
     * {@inheritDoc}
     */
    public Subject login(String username, String password, WebSecurityContext context) {
        HttpServletRequest request = context.getRequest();
        String jossoSessionId = this.getJossoSessionId(context);
        if (jossoSessionId == null) {
            return null;
        } else {
            return this.loginWith(jossoSessionId, request);
        }
    }

    /**
     * Obtém o josso session id do contexto
     * 
     * @param context context
     * @return josso session id
     */
    private String getJossoSessionId(WebSecurityContext context) {
        Cookie[] cookies = context.getRequest().getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (Constants.JOSSO_SINGLE_SIGN_ON_COOKIE.equals(cookie.getName()) && cookie.getValue().length() > 0) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    /**
     * Usando um session id, verifica
     * 
     * @param sessionId String
     * @param request {@link HttpServletRequest}
     * @return {@link Subject}
     */
    private Subject loginWith(String sessionId, HttpServletRequest request) {
        this.getTransactionalController().startTransaction();
        try {
            SSOAgent jossoAgent = Lookup.getInstance().lookupSSOAgent();

            SSOSession session = jossoAgent.getSSOSessionManager().getSession(this.requester, sessionId);
            SSORole[] roles = jossoAgent.getSSOIdentityManager().findRolesBySSOSessionId(this.requester, sessionId);
            final Set<Permission> pubCredentials = new HashSet<Permission>();
            User user = this.createUser();
            for (SSORole ssoRole : roles) {
                String string = ssoRole.getName();
                List<Permission> list = this.parsePermission(string);
                pubCredentials.addAll(list);
            }
            pubCredentials.addAll(this.getPubCredentials(user));
            Set<Principal> principals = new HashSet<Principal>();
            principals.add(new SecurityPrincipal(session.getUsername()));
            this.logUserLoginData(sessionId, session.getUsername(), this.requester);
            return new Subject(true, principals, pubCredentials, Collections.emptySet());
        } catch (Exception e) {
            LogFactory.getLog(JOSSOSecurityManager.class).warn("Cannot login", e);
        } finally {
            this.getTransactionalController().commit();
        }
        return null;
    }

    /**
     * {@inheritDoc}
     * 
     * @see bancosys.tec.security.SecurityManager#logout(javax.servlet.http.HttpServletRequest)
     */
    public void logout(WebSecurityContext context) {
        context.getRequest().getSession().invalidate();
    }

    /**
     * {@inheritDoc}
     */
    public void storeSubject(WebSecurityContext context, Subject subject) {
        HttpApplicationSecurityManager.storeSubjectIntoWebSecurityContext(context, subject);
    }

    /**
     * @return the requestor
     */
    public String getRequester() {
        return this.requester;
    }

    /**
     * @param requestor the requestor to set
     */
    public void setRequester(String requestor) {
        this.requester = requestor;
    }
}
